//SPDX-FileCopyrightText: Copyright 2022-2024 深圳市同心圆网络有限公司
//SPDX-License-Identifier: GPL-3.0-only

package user_api_serv

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"strings"
	"time"

	"atomgit.com/openlinksaas/api-server/api_common"
	"atomgit.com/openlinksaas/api-server/dao"
	"atomgit.com/openlinksaas/api-server/dao/user_dao"
	"atomgit.com/openlinksaas/proto-gen-go.git/user_api"
	"github.com/dchest/uniuri"
	"github.com/google/uuid"
	"go.mongodb.org/mongo-driver/mongo"
)

func getJihulabAccessToken(clientId, clientSecret, code string) (string, string, error) {
	// redirect_uri=REDIRECT_URI
	query := make(url.Values)
	query.Add("grant_type", "authorization_code")
	query.Add("client_id", clientId)
	query.Add("client_secret", clientSecret)
	query.Add("code", code)
	query.Add("redirect_uri", "https://www.linksaas.pro/callback/jihulab")

	reqUrl := fmt.Sprintf("https://jihulab.com/oauth/token?%s", query.Encode())
	res, err := http.Post(reqUrl, "application/x-www-form-urlencoded", nil)

	if err != nil {
		return "", "", err
	}
	defer res.Body.Close()
	resData, err := io.ReadAll(res.Body)
	if err != nil {
		return "", "", err
	}
	resObj := map[string]any{}
	err = json.Unmarshal(resData, &resObj)
	if err != nil {
		return "", "", err
	}
	//失败 {"error":"invalid_grant","error_description":"The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."}
	//成功 {"access_token":"7161e4979455ce650f311fa5cde227ab2462f6778abcc1c7acb3f7bc1ee1ea3e","token_type":"Bearer","expires_in":7200,"refresh_token":"8144d8f7c6af829d9cefc8759909835f22c57489878ecbec12c6c49b74198acf","scope":"read_user read_repository","created_at":1716976131}
	errMsg, ok := resObj["error"]
	if ok {
		return "", "", fmt.Errorf(errMsg.(string))
	}
	accessToken := resObj["access_token"]
	refreshToken := resObj["refresh_token"]
	if accessToken == nil || refreshToken == nil {
		return "", "", fmt.Errorf("wrong response")
	}
	return accessToken.(string), refreshToken.(string), nil
}

func refreshJihulabAccessToken(clientId, clientSecret, refreshToken string) (string, string, error) {
	query := make(url.Values)
	query.Add("client_id", clientId)
	query.Add("client_secret", clientSecret)
	query.Add("refresh_token", refreshToken)
	query.Add("grant_type", "refresh_token")
	query.Add("redirect_uri", "https://www.linksaas.pro/callback/jihulab")

	reqUrl := fmt.Sprintf("https://jihulab.com/oauth/token?%s", query.Encode())
	res, err := http.Post(reqUrl, "application/x-www-form-urlencoded", nil)

	if err != nil {
		return "", "", err
	}
	defer res.Body.Close()
	resData, err := io.ReadAll(res.Body)
	if err != nil {
		return "", "", err
	}
	resObj := map[string]any{}
	err = json.Unmarshal(resData, &resObj)
	if err != nil {
		return "", "", err
	}
	//失败 {"error":"invalid_grant","error_description":"The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client."}
	//成功 {"access_token":"3e18be641348a6237212208e538f58ffb3825b4038c0991d415bfdb2e06bc167","token_type":"Bearer","expires_in":7200,"refresh_token":"edcaa95f31b2095c338cc70be452b36d2fe394af1a96cb4c098ee3f0f02f0bf3","scope":"read_user read_repository","created_at":1716976536}
	//TODO
	errMsg, ok := resObj["error"]
	if ok {
		return "", "", fmt.Errorf(errMsg.(string))
	}
	accessToken := resObj["access_token"]
	newRefreshToken := resObj["refresh_token"]
	if accessToken == nil || newRefreshToken == nil {
		return "", "", fmt.Errorf("wrong response")
	}
	return accessToken.(string), newRefreshToken.(string), nil
}

func getJihulabUserName(accessToken string) (string, string, string, error) {
	reqUrl := fmt.Sprintf("https://jihulab.com/api/v4/user?access_token=%s", accessToken)
	res, err := http.Get(reqUrl)
	if err != nil {
		return "", "", "", err
	}
	defer res.Body.Close()
	resData, err := io.ReadAll(res.Body)
	if err != nil {
		return "", "", "", err
	}
	resObj := map[string]any{}
	err = json.Unmarshal(resData, &resObj)
	if err != nil {
		return "", "", "", err
	}
	//失败 {"message":"401 Unauthorized"}
	//成功 {"id":16536,"username":"user_linksaas","name":"鲨 凌","state":"active","locked":false,"avatar_url":"https://jihulab.com/uploads/-/system/user/avatar/16536/avatar.png","web_url":"https://jihulab.com/user_linksaas","created_at":"2022-08-28T15:45:12.444+08:00","bio":"","location":"","public_email":"","skype":"","linkedin":"","twitter":"","discord":"","website_url":"","organization":"","job_title":"","pronouns":"","bot":false,"work_information":null,"local_time":null,"last_sign_in_at":"2024-05-29T15:57:29.842+08:00","confirmed_at":"2022-08-28T15:45:23.928+08:00","last_activity_on":"2024-05-29","email":"panleiming@linksaas.pro","theme_id":1,"color_scheme_id":1,"projects_limit":100000,"current_sign_in_at":"2024-05-29T17:36:28.825+08:00","identities":[],"can_create_group":true,"can_create_project":true,"two_factor_enabled":false,"external":false,"private_profile":false,"commit_email":"panleiming@linksaas.pro","shared_runners_minutes_limit":null,"extra_shared_runners_minutes_limit":null,"scim_identities":[],"phone":"+8613760735307"}
	errDesc, ok := resObj["message"]
	if ok {
		return "", "", "", fmt.Errorf(errDesc.(string))
	}

	login := resObj["username"]
	name := resObj["name"]
	avatarUrl := resObj["avatar_url"]

	if login == nil || name == nil || avatarUrl == nil {
		return "", "", "", fmt.Errorf("wrong response")
	}
	return "jihulab:" + login.(string), name.(string), avatarUrl.(string), nil
}

func (apiImpl *UserApiImpl) loginJihulab(ctx context.Context, req *user_api.LoginRequest) (*user_api.LoginResponse, error) {
	//获取accessToken
	accessToken, refreshToken, err := getJihulabAccessToken(apiImpl.servCfg.Jihulab.ClientId, apiImpl.servCfg.Jihulab.ClientSecret, req.Passwd)
	if err != nil {
		return nil, err
	}
	//获取用户信息
	userName, displayName, logoUri, err := getJihulabUserName(accessToken)
	if err != nil {
		return nil, err
	}
	//检查用户是否存在
	userInfoItem, err := user_dao.UserInfoDao.GetByName(ctx, userName)
	if err != nil { //用户不存在
		dbSess, err := dao.StartSession()
		if err != nil {
			return nil, err
		}
		defer dao.EndSession(dbSess)

		userId, err := dbSess.WithTransaction(ctx, func(sessCtx mongo.SessionContext) (interface{}, error) {
			userId := uuid.NewString()
			err = api_common.CreateUser(sessCtx, userId, userName, displayName, logoUri, "", false, user_api.USER_TYPE_USER_TYPE_JIHU_LAB)
			if err != nil {
				return "", err
			}
			return userId, nil
		})
		if err != nil {
			return nil, err
		}
		userInfoItem, err = user_dao.UserInfoDao.Get(ctx, userId.(string))
		if err != nil {
			return &user_api.LoginResponse{
				Code:   user_api.LoginResponse_CODE_WRONG_USERNAME,
				ErrMsg: "用户不存在",
			}, nil
		}
	} else {
		if userInfoItem.BasicInfo.DisplayName != displayName || userInfoItem.BasicInfo.LogoUri != logoUri {
			err = user_dao.UserInfoDao.UpdateBasicInfo(ctx, userInfoItem.UserId, &user_dao.BasicUserInfoDbItem{
				DisplayName: displayName,
				LogoUri:     logoUri,
			})
			if err != nil {
				return nil, err
			}
		}
	}
	userInfoItem.BasicInfo.DisplayName = displayName
	userInfoItem.BasicInfo.LogoUri = logoUri
	//创建会话
	secretItem, err := user_dao.UserSecretDao.Get(ctx, userInfoItem.UserId)
	if err != nil {
		secretItem = &user_dao.UserSecretDbItem{}
	}
	//创建会话
	sessionId := uniuri.NewLen(32) + strings.ReplaceAll(uuid.NewString(), "-", "")

	dao.CacheDao.SetSession(ctx, &dao.SessionDbItem{
		SessionId:         sessionId,
		UserId:            userInfoItem.UserId,
		UserName:          userInfoItem.UserName,
		DisplayName:       userInfoItem.BasicInfo.DisplayName,
		LogoUri:           userInfoItem.BasicInfo.LogoUri,
		UserType:          uint32(user_api.USER_TYPE_USER_TYPE_JIHU_LAB),
		TestAccount:       userInfoItem.TestAccount,
		ExtraAccessToken:  accessToken,
		ExtraRefreshToken: refreshToken,
		ExtraLastRefresh:  time.Now().Unix(),
	})

	go sendOnlineNotice(userInfoItem.UserId)

	return &user_api.LoginResponse{
		Code:       user_api.LoginResponse_CODE_OK,
		SessionId:  sessionId,
		UserInfo:   userInfoItem.ToUserInfo(),
		UserSecret: secretItem.Secret,
		ExtraToken: accessToken,
	}, nil
}
